﻿--[[
    Author: Valana <Team BATTLE> of Mug'thol Horde-US
]]

local _addonName, _addonTitle =  GetAddOnInfo("TB_Core")
local TbCore = LibStub("AceAddon-3.0"):NewAddon(_addonName, "AceConsole-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale(_addonName, true)

local _G = _G

local _player = UnitName("player")
local _capitalizations
local _caseInsensitivePatterns
local _colors =
{
    RED     = { r = 1, g = 0, b = 0 }, ORANGE = { r = 1, g = .5, b = 0 },
    YELLOW  = { r = 1, g = 1, b = 0 }, GREEN  = { r = 0, g = 1, b = 0 },
    LTBLUE  = { r = 0, g = 1, b = 1 }, BLUE   = { r = 0, g = 0, b = 1 },
    PURPLE  = { r = 1, g = 0, b = 1 }, WHITE  = { r = 1, g = 1, b = 1 },
    BLACK   = { r = 0, g = 0, b = 0 }, PINK   = { r = 1, g = .5, b = 1 },
    GUILD   = ChatTypeInfo["GUILD"],
    OFFICER = ChatTypeInfo["OFFICER"],
    SYSTEM = ChatTypeInfo["SYSTEM"],
    WHISPER = ChatTypeInfo["WHISPER"],
}


function TbCore:OnInitialize()
    -- Define shorter aliases for the functions provided in this library.
    function TPrint(msg, frameNumber, color) return self:Print(msg, frameNumber, color) end
    function TDebug(msg, frameNumber, color) return self:Debug(msg, frameNumber, color) end
    function TEPrint(msg, color, t) return self:PrintError(msg, color, t) end

    -- Initialize our metatables
    _capitalizations = setmetatable({ }, { __index =
        function(tbl, phrase)
            local capitalized = self:StrTrim(gsub(phrase, "^([\192-\255]?%a?[\128-\191]*)(.*)$", function(firstLetter, word) return strupper(firstLetter) .. strlower(word) end))
            rawset(tbl, phrase, capitalized)
            return capitalized
        end })

    _caseInsensitivePatterns = setmetatable({ }, { __index =
        function(tbl, phrase)
            local pattern = gsub(phrase, "([\192-\255]?%a?[\128-\191]*)",
                function(ch)
                    local upper, lower = strupper(ch), strlower(ch)
                    if lower == upper then return ch end
                    return format("[%s%s]", upper, lower)
                end)
            rawset(tbl, phrase, pattern)
            return pattern
        end })

    -- Define the /gx command.
    self:RegisterChatCommand("GX", RestartGx)
end

function TbCore:OnEnable()
	self:ValanaOnly()

	-- Increase the games's bloodiness.
	SetCVar("violenceLevel", 5)

    -- Show enemy cast bars.
    SetCVar("showArenaEnemyCastbar", 1)
    SetCVar("ShowTargetCastbar", 1)
    SetCVar("ShowVKeyCastbar", 1)

    -- Increase the quality of screenshots to 90% for JPEG's (default: 30%).
	SetCVar("ScreenshotQuality", max(9, tonumber(GetCVar("ScreenshotQuality"))))
end

-- Print a message to a chat window.
function TbCore:Print(msg, frameNumber, color, hide)
    if not msg or hide then return end
    msg = tostring(msg)

    -- Get the RGB components of the requested display color for the message.
    local red, green, blue = self:ColorToRgb(color)

    -- Determine which frame to send the message to.
    if not frameNumber or type(frameNumber) ~= "number" or frameNumber > NUM_CHAT_WINDOWS then
        frameNumber = 1
    end
    _G["ChatFrame" .. frameNumber]:AddMessage(msg, red or 1, green or 1, blue or 1)
end

-- Print a debug message to a chat window.
function TbCore:Debug(msg, frameNumber, color)
    self:Print(msg, frameNumber, color, self:IsValana())
end

-- Print to every chat frame displaying the specified channel.
function TbCore:PrintForChannel(message, channel, color)
    for i = 1, NUM_CHAT_WINDOWS do
        self:PrintForChannelHelper(message, channel, i, color, GetChatWindowMessages(i))
    end
end

-- Print to the specified chat frame if it accepts the specified chat type (channel).
function TbCore:PrintForChannelHelper(message, channel, index, color, accepted, ...)
    if accepted == channel then
        self:Print(message, index, color)
    elseif accepted then
        -- Recurse with the next accepted chat types.
        self:PrintForChannelHelper(message, channel, index, color, ...)
    end
end

-- Print a message to the error frame.
function TbCore:PrintError(msg, color, holdTime)
    if not msg then return end
    msg = tostring(msg)

    -- Get the hex triplet of the requested display color for the message.
    local red, green, blue = self:ColorToRgb(color)

    -- Display the message in the error frame using the requested color and display time, or defaults if unspecified.
    UIErrorsFrame:AddMessage(msg, red or 1, green or 1, blue or 1, holdTime or UIERRORS_HOLD_TIME)
end

-- Print a message to the raid boss emote frame.
function TbCore:PrintRaidBossEmote(message, playSound)
    RaidNotice_AddMessage(RaidBossEmoteFrame, message, ChatTypeInfo["RAID_BOSS_EMOTE"])
    if playSound then PlaySound("RaidWarning") end
end

-- Figure out if we have permission to invite people.
function TbCore:CanInvite()
    return not self:IsGrouped() or ((IsRaidLeader() or IsRaidOfficer()) and self:GroupHasRoom())
end

-- Figure out if we are able to use the Raid Warning channel.
function TbCore:CanUseRaidWarning()
    return UnitInRaid("player") and (IsRaidLeader() or IsRaidOfficer())
end

-- Figure out if we can uninvite the specified unit.
function TbCore:CanUninviteUnit(unit)
    return UnitExists(unit) and not UnitIsUnit("player", unit) and self:IsGroupedWith(unit) and (IsRaidLeader() or (IsRaidOfficer() and not UnitIsPartyLeader(unit)))
end

-- Figure out if we have room for more players.
function TbCore:GroupHasRoom()
    return not self:IsGrouped() or (not UnitInRaid("player") and GetNumPartyMembers() < 4) or GetNumRaidMembers() < 40
end

-- Get the string representation of our group type.
function TbCore:GetRaidOrParty()
    if TbCore:IsInRaid() then
        return "RAID"
    elseif GetNumPartyMembers() > 0 then
        return "PARTY"
    end
end

-- Determine whether we are grouped.
function TbCore:IsGrouped()
	-- UnitInParty("player") always returns 1 whether the player is grouped or not.
    return TbCore:IsInRaid() or GetNumPartyMembers() > 0
end

-- Determine whether we are in a 5-player party.
function TbCore:IsInParty()
	-- UnitInParty("player") always returns 1 whether the player is grouped or not.
    return not TbCore:IsInRaid() and GetNumPartyMembers() > 0
end

-- Determine whether we are in a raid.
function TbCore:IsInRaid()
    return UnitInRaid("player")
end

-- Figure out if we are grouped with the specified unit.
function TbCore:IsGroupedWith(unit)
    return UnitInRaid(unit) or UnitInParty(unit)
end

-- Figure out if we are currently in a battleground.
function TbCore:IsInBattleground()
    local _, t = IsInInstance()
    return t == "pvp"
end

-- Figure out if we are currently waiting in a rated battleground queue.
function TbCore:IsInRatedArenaQueue()
    for i = 1, MAX_BATTLEFIELD_QUEUES do
        local status, _, _, _, _, teamSize, rated = GetBattlefieldStatus(i)
        if teamSize > 5 and rated == 1 and status == "confirm" then
            return true
        end
    end
end

-- Figure out if we are currently in an arena.
function TbCore:IsInArena()
    local _, t = IsInInstance()
    return t == "arena"
end

-- Figure out if we are currently waiting in a rated arena queue.
function TbCore:IsInRatedArenaQueue()
    for i = 1, MAX_BATTLEFIELD_QUEUES do
        local status, _, _, _, _, teamSize, rated = GetBattlefieldStatus(i)
        if teamSize < 10 and rated == 1 and status == "confirm" then
            return true
        end
    end
end

-- Figure out if we are currently in a PvP instance.
function TbCore:IsInPvPInstance()
    return self:IsInBattleground() or self:IsInArena()
end

-- Figure out if we are currently in a 5-person dungeon.
function TbCore:IsInDungeon()
    local _, t = IsInInstance()
    return t == "party"
end

-- Figure out if we are currently in a raid dungeon.
function TbCore:IsInRaidInstance()
    local _, t = IsInInstance()
    return t == "raid"
end

-- Figure out if we are currently in a PvE instance.
function TbCore:IsInPvEInstance()
    local _, t = IsInInstance()
    return t == "party" or t == "raid"
end

-- Figure out if we are currently in an instance.
function TbCore:IsInInstance()
    return not self:IsNotInInstance()
end

-- Figure out if we are currently in the outside world.
function TbCore:IsNotInInstance()
    local _, t = IsInInstance()
    return t == "none"
end

-- Always trim and capitalize name before calling IsFriend().
function TbCore:IsFriend(name)
    if not name then return end

	-- First check the actual friends list.
	for i = 1, GetNumFriends() do
		if GetFriendInfo(i) == name then
			return true
		end
	end

	-- Check Real ID friends if that didn't work.
	local realm, _, numBnFriends = GetRealmName(), BNGetNumFriends()
	for i = 1, numBnFriends do
		for j = 1, BNGetNumFriendToons(i) do
			local _, rName, rGame, rRealm = BNGetFriendToonInfo(i, j)
			if rName == name and rGame == "WoW" and rRealm == realm then
				return true
			end
		end
	end
end

-- Always trim and capitalize name before calling IsOnlineGuildMate().
function TbCore:IsOnlineGuildMate(name)
	if not UnitIsInMyGuild(name) then return end

	-- Walk only the list of online guild mates to find the specified player.
	local wasShowingOffline, result = GetGuildRosterShowOffline()
	SetGuildRosterShowOffline(false)
	local _, numGuildMates = GetNumGuildMembers()
	for i = numGuildMates, 1, -1 do
		if GetGuildRosterInfo(i) == name then
			result = true
			break
		end
	end

	-- Restore our original roster state before returning our findings.
	SetGuildRosterShowOffline(wasShowingOffline)
	return result
end

-- Convert the color with the specified name to its RGB components.
function TbCore:ColorToRgb(color)
    -- Default to white if no color, or an invalid one, was specified.
    local c = _colors[strupper(color or "WHITE")] or _colors[strupper("WHITE")]
    return c.r, c.g, c.b
end

-- Convert a color's RGB components to its hexadecimal string representation.
function TbCore:RgbToHex(r, g, b, a)
    return format("%02x%02x%02x%02x", self:ClampRgbValue(a, 1) * 255, self:ClampRgbValue(r, 0) * 255, self:ClampRgbValue(g, 0) * 255, self:ClampRgbValue(b, 0) * 255)
end

function TbCore:ClampRgbValue(component, default)
    return max(0, min(component or default or 0, 255))
end

-- Find the remaining amount of time remaining on the specified spell, in seconds.
function TbCore:GetRemainingCooldown(spell)
    if type(spell) == "number" then
        spell = GetSpellInfo(spell)
    end
    local startTime, cooldown = GetSpellCooldown(spell)
    if not startTime then return end
    return max(0, startTime + cooldown - GetTime())
end

-- Determine if we have a certain talent.
function TbCore:HasTalent(tree, slot)
    local _, _, _, _, rank = GetTalentInfo(tree, slot)
    return rank > 0
end

-- Count the number of entries in the specified dictionary.
function TbCore:CountDictionaryEntries(dictionary)
    -- First check if this is an array. Note that if it is a mixed table, the count will be incorrect.
    local count = #dictionary
    if count > 0 then
        return count
    end

    -- Do an actual count of the number of entries.
    for key, value in pairs(dictionary) do
        count = count + 1
    end
    return count
end

-- Build a sorted array from the specified dictionary.
function TbCore:GetSortedArray(dictionary, comparer)
    local array = dictionary
    if #dictionary == 0 then
        array = { }
        for key, value in pairs(dictionary) do
            tinsert(array, key)
        end
    end

    sort(array, comparer)
    return array
end

-- Determine whether a string is nil or entirely made up of white space.
function TbCore:StrIsNilOrEmpty(str)
    return not str or not strfind(str, "[^%s]")
end

function TbCore:StrIsNotNilOrEmpty(str)
    return str and strfind(str, "[^%s]")
end

-- Trim white spaces and the specified delete string from the start and end of the specified string if it's not nil.
function TbCore:StrTrim(value, delete)
    if not value then return end

    if self:StrIsNotNilOrEmpty(delete) then
        delete = "%s*" .. delete .. "*%s*"
    else
        delete = "%s*"
    end

    -- Remove the pattern from the start and from the end, then trim the result.
    return gsub(value, "^" .. delete .. "(.-)" .. delete .. "$", "%1")
end

-- Trim the specified string of the specified substring, and cast it to lower case.
function TbCore:StrLowerTrim(value, delete)
    if not value then return end
    return strlower(self:StrTrim(value, delete))
end

-- Capitalize the first letter of the specified word in the specified string, while converting all other letters to lowercase.
function TbCore:Capitalize(word)
	word = self:StrTrim(word)
    if not word then return end

    return _capitalizations[word]
end

-- Get a pattern which can be used to search for the specified phrase case-insensitively.
function TbCore:GetCaseInsensitivePattern(phrase)
    if not phrase then return end

    return _caseInsensitivePatterns[phrase]
end

-- Extract the visible text from a hyperlink, or return the parameter itself if it is not a hyperlink.
function TbCore:GetTextFromHyperlink(link)
    if not link then return end
    link = tostring(link)

    -- Parse real hyperlinks.
    local text = strmatch(link, "\124H.-\124h%[(.+)%]\124h")
    if not text then
        -- Try to parse fake hyperlinks by removing any brackets.
        text = strmatch(link, "%[*([^]]+)")
    end
    return self:StrTrim(text)
end

-- Converts a time specified in seconds into a more legible format.
function TbCore:GetFormattedTime(value)
    -- Calculate the number of hours.
    local hours, minutes
    local seconds = abs(value)
    hours = floor(seconds / 3600)
    seconds = seconds % 3600

    -- Calculate the number of minutes. What remains is the number of seconds.
    minutes = floor(seconds / 60)
    seconds = ceil(seconds % 60)

    local formatted = ""
    if value < 0 then formatted = "-" end
    if hours > 0 then formatted = formatted .. hours .. L["H"] .. " " end
    if minutes > 0 then formatted = formatted .. minutes .. L["MN"] .. " " end
    if seconds > 0 then formatted = formatted .. seconds .. L["S"] end

    return self:StrTrim(formatted)
end

function TbCore:NewQueue(array)
	-- Initialize a queue object. Hide the internal storage to protect direct access into it.
	local queue = { first = 1, last = 0, count = 0, array = { } }

	-- Add the Enqueue method to add items to the back of the queue.
	function queue:Enqueue(item, ...)
		if item then
			-- Add the first specified item to the back of the queue.
			self.last = self.last + 1
			self.array[self.last] = item
			self.count = self.count + 1

			-- Process any additional items.
			self:Enqueue(...)
		end
	end

	-- Define the Dequeue method to pop the item at the head of the queue.
	function queue:Dequeue()
		-- Use the same behavior as tremove, which returns nil when the array is empty rather than throwing an error.
		if self.count == 0 then return end

		-- Pop the first item.
		local item = self.array[self.first]
		self.array[self.first] = nil
		self.count = self.count - 1

		-- Reset our indices whenever our queue is empty, as a means of reclaiming lost storage.
		if self.count == 0 then
			self.first = 1
			self.last = 0
		else
			self.first = self.first + 1
		end

		-- Produce the dequeued item.
		return item
	end

	function queue:IsQueued(check)
		for _, item in pairs(self.array) do
			if item == check then
				return true
			end
		end
	end

	-- Create a Clear method to reset a queue to its initial state.
	function queue:Clear()
		wipe(self.array)
		self.first = 1
		self.last = 0
		self.count = 0
	end

	-- Add any initial elements.
	if array then
		for _, item in pairs(array) do
			queue:Enqueue(item)
		end
	end

	-- Return the initialized queue.
	return queue
end

function TbCore:IsValana()

end

function TbCore:ValanaOnly()

end
